/**
 * Copyright (c) 2007,中企动力华南研发中心
 * All rights reserved.
 */
package com.ce.pms.common.util;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Random;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.springframework.core.io.ClassPathResource;
import com.ce.pms.house.vo.ImageVO;
import com.ce.util.BlankUtil;
import com.sitechasia.webx.core.utils.populator.ConverterFactory;
import com.sitechasia.webx.core.utils.populator.ITypeConverter;

/**
 * 功能描述：上传文件解决方案工具类。<br>
 * 实施步骤：
 *       <ol>
 *          <li>因受限于javascript,不能用ajax提交，可以使用ifarme提交</li>
 *          <li>jsp中，给对应表单增加属性ENCTYPE="multipart/form-data"</li>
 *          <li>action中,初始化UploadFile类</li>
 *          <li>action中,调用方法upload或parseFormAndUpload，将得到文件名及路径</li>
 *          <li>service中,捕获所有异常，当出现故障时，调用deleteFile方法</li>
 *          <li>将捕获到的异常向外抛</li>
 *       </ol>
 * @author 罗军林 创建日期：2008-6-26
 */
public class UpLoadFile{
    /**
     * 配置文件的路径
     */
    final static private String configPath="config/uploadfile.properties";

    /**
     * 最多只允许在内存中存储的数据,单位:字节
     */
    private int sizeThreshold;

    /**
     * 设置允许上传的最大文件字节数
     */
    private long sizeMax;

    /**
     * 文件的上传路径
     */
    private String uploadImagesPath;

    /**
     * 充许上传的文件类型,示例：jpg,gif
     */
    private String allowUploadFileTypes;


    /**
     * 所有表单域的值将存入本HashMap中
     */
    private HashMap<String,String> fieldValue=new HashMap<String, String>();

    public UpLoadFile(){
        this.sizeThreshold=4096;
        this.sizeMax=1024*1024;
        this.uploadImagesPath="uploadimages";
        this.fieldValue=new HashMap<String, String>();
    }

    public UpLoadFile(final int sizeThreshold,final long sizeMax,final String uploadImagesPath){
        this.sizeThreshold=sizeThreshold;
        this.sizeMax=sizeMax;
        this.uploadImagesPath=uploadImagesPath;
    }

    /**
     * Function:<p>上传文件，文件上传返回文件名、路径、表单域名称
     * @param request
     * @param voObjectBean
     * @return
     * @throws Throwable
     */
    public List<ImageVO> upload(final HttpServletRequest request) throws Throwable {
        List<ImageVO> imageList=new ArrayList<ImageVO>();
        List fileItems = getFieldList(request);
        // 依次处理每个上传的文件
        Iterator iter = fileItems.iterator();
        while (iter.hasNext()) {
            FileItem item = (FileItem)iter.next();
            //忽略其他不是文件域的所有表单信息
            if (!item.isFormField()) {
                    doUpLoadFile(imageList, item);
            }
        }
        return imageList;
    }

    /**
     * Function:<p>上传文件并解析表单域并将值存入voObjectBean中,主要经对表单设置
     * 				了ENCTYPE="multipart/form-data"属性时，使用本方法解析</p>
     * @param request
     * @param voObjectBean
     * @throws Throwable
     * Create author:罗军林
     * Create on:2008-6-26
     * Edit author:
     * Edit on:
     * Why:
     */
    public List<ImageVO> parseFormAndUpload(final HttpServletRequest request,final Object voObjectBean) throws Throwable {
        List<ImageVO> imageList=new ArrayList<ImageVO>();

        upLoadFiles(request, fieldValue, imageList);

        if(voObjectBean!=null && fieldValue!=null){
            convertHaspmapToObject(voObjectBean, fieldValue);
        }
        return imageList;
    }

    /**
     * Function:上传文件并解析表单域，表单值存入haspmap中并返回,主要经对表单设置了ENCTYPE="multipart/form-data"属性时，使用本方法解析
     * @param request
     * @param voObjectBean
     * @throws Throwable
     * Create author:罗军林
     * Create on:2008-6-26
     * Edit author:
     * Edit on:
     * Why:
     */
    public List<ImageVO> parseFormAndUploadWithMap(final HttpServletRequest request,final HashMap<String,String> fieldValue) throws Throwable {
        List<ImageVO> imageList=new ArrayList<ImageVO>();
        upLoadFiles(request, fieldValue, imageList);
        return imageList;
    }

    /**
     * 功能描述:根据表单域的名字返回其value<br>
     * 注：必须先调用parseFormAndUpload(HttpServletRequest request,Object voObjectBean)方法<br>
     * @param key
     * @return<br>
     * Create author:罗军林<br>
     * Create on:2008-7-23<br>
     * Edit author:<br>
     * Edit on:<br>
     * Why:<br>
     */
    public String getAttrib(final String key){
        return fieldValue.get(key);
    }

	/**
	 * 功能描述:  主要用于上传多张图片时,处理除新增上传图片后的所有剩余图片(针对修改初始化时显示的图片进行处理)<br>
	 * @param ext 区分不同上传组件的后缀名
	 * @param set 图片集合
	 * Create author:yandawei <br>
	 * Create on:2008-7-24  <br>
	 * Edit author:  <br>
	 * Edit on:    <br>
	 * Why:     <br>
	 */
	public void wrapImageSet(String ext, Set<ImageVO> set){
		if(getAttrib("file_size_"+ext) != null && set != null){
			for(int i = 0 ; i < Integer.parseInt(getAttrib("file_size_"+ext));i++){
				if(getAttrib("id_"+i+"_"+ext) != null){
					ImageVO image = new ImageVO();
					image.setId(Long.valueOf(getAttrib("id_"+i+"_"+ext)));
					image.setName(getAttrib("name_"+i+"_"+ext));
					image.setPath(getAttrib("path_"+i+"_"+ext));
					set.add(image);
				}
			}
		}
	}

	/**
	 * 功能描述:  主要用于上传一张图片时,处理除新增上传图片后的所有剩余图片(针对修改初始化时显示的图片进行处理)<br>
	 * @param ext 区分不同上传组件的后缀名10
	 * @param image ImageVO
	 * Create author:yandawei <br>
	 * Create on:2008-7-24  <br>
	 * Edit author:  <br>
	 * Edit on:    <br>
	 * Why:     <br>
	 */
	public ImageVO wrapImage(String ext, ImageVO image){
		if(BlankUtil.isBlank(image)){
			image = new ImageVO();
		}
		if(getAttrib("id_0_"+ext)!=null){
			image.setId(Long.valueOf(getAttrib("id_0_"+ext)));
		}
		if(getAttrib("name_0_"+ext)!=null){
			image.setName(getAttrib("name_0_"+ext));
		}
		if(getAttrib("path_0_"+ext)!=null){
			image.setPath(getAttrib("path_0_"+ext));
		}
		return image;
	}


    /**
     * Function:删除文件
     * @param path
     * @param name
     * @return
     * Create author:罗军林
     * Create on:2008-6-26
     * Edit author:
     * Edit on:
     * Why:
     */
    static public Boolean deleteFile(final String path,final String name){
    	File file = getFileSystem(path, name);
        return file.delete();
    }


    /**
     * 功能描述:根据传进来的imageVO,删除该文件<br>
     * path,name这个两个属性不能为空
     * @param imageVO
     * @return<br>
     * Create author:罗军林<br>
     * Create on:2008-7-16<br>
     * Edit author:<br>
     * Edit on:<br>
     * Why:<br>
     */
    static public Boolean deleteFile(final ImageVO imageVO){
        boolean result=false;
        if( imageVO == null ){
            result=false;
        }else{
        	result=deleteFile(imageVO.getPath(),imageVO.getName());
        }
        return result;
    }

    /**
     * 功能描述:  :根据传进来的imageVO List,删除该文件列表<br>
     * @param imageList
     * @return
     * Create author:yixianjian <br>
     * Create on:2008-7-22  <br>
     * Edit author:  <br>
     * Edit on:    <br>
     * Why:     <br>
     * @throws IOException
     */
    static public Boolean deleteFile(final List<ImageVO> imageList){
        boolean result = false;
        if(imageList == null){
            result=false;
        }else{
            for(ImageVO imageItem: imageList){
                result = deleteFile(imageItem);
            }
        }
        return result;
    }

    /**
     * 功能描述:根据imageVO返回字节数组<br>
     * @param imageVO
     * @return
     * @throws FileNotFoundException
     * @throws IOException<br>
     * Create author:罗军林<br>
     * Create on:2008-7-22<br>
     * Edit author:<br>
     * Edit on:<br>
     * Why:<br>
     */
    static public byte[] getFileInputStream(final ImageVO imageVO) throws FileNotFoundException, IOException{
        byte[] data=null;
        if(null!=imageVO){
            File file=getFileSystem(imageVO.getPath(), imageVO.getName());
            InputStream inputStream=new FileInputStream(file);
             data=new byte[inputStream.available()];
             inputStream.read(data);  //读数据
             inputStream.close();
        }
        return data;
    }

    /**
     * 功能描述:读取配置文件的上传文件路径<br>
     * Create author:罗军林<br>
     * Create on:2008-7-21<br>
     * Edit author:<br>
     * Edit on:<br>
     * Why:<br>
     */
    static public String getUploadFilePath(){
        Properties propert=PropertiesManage.getInstance(configPath);
        return propert.getProperty("upLoadFilePath");
    }

    /**
     * 功能描述:  获取配置文件中的key对应的值<br>
     * @param key
     * @return
     * Create author:yixianjian <br>
     * Create on:2008-9-24  <br>
     */
    static public String getProperty(String key){
        Properties propert=PropertiesManage.getInstance(configPath);
        return propert.getProperty(key);
    }

    /****私有方法******************************************/

    /**
     * Function:取得所有表单域的流列表
     * @param request
     * @return
     * @throws FileUploadException
     * Create author:罗军林
     * Create on:2008-6-26
     * Edit author:
     * Edit on:
     * Why:
     */
    private List getFieldList(final HttpServletRequest request) throws FileUploadException {
        DiskFileItemFactory factory=new DiskFileItemFactory();
        factory.setSizeThreshold(sizeThreshold);
        ServletFileUpload fileUpload=new ServletFileUpload(factory);
        fileUpload.setSizeMax(sizeMax);
        //开始读取上传信息
        return fileUpload.parseRequest(request);
    }


    /**
     * Function:单个文件上传
     * @param request
     * @param imageList
     * @param item
     * @throws Throwable
     * @throws Exception
     * Create author:罗军林
     * Create on:2008-6-26
     * Edit author:
     * Edit on:
     * Why:
     */
    private void doUpLoadFile(final List<ImageVO> imageList,final FileItem item) throws Throwable{
        String name = item.getName();
        long size = item.getSize();
        if((name!=null && !name.equals("")) && size>0){
            StringBuffer path=new StringBuffer();
            path.append(this.uploadImagesPath).append(File.separator);
            String suffix=name.substring(( name.lastIndexOf('.') ));
            ImageVO imageVO=generationNotExistFileName(path.toString(),suffix);
            imageVO.setFieldName(item.getFieldName());
            imageList.add(imageVO);
            //保存上传的文件到指定的目录
            item.write(new File(getUploadFilePath()+imageVO.getPath()+imageVO.getName()));
        }
    }

    /**
     * Function:将HashMap的值转换到指定对象中
     * @param obj
     * @param map
     * @throws IntrospectionException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws Exception
     * Create author:罗军林
     * Create on:2008-6-25
     * Edit author:
     * Edit on:
     * Why:
     */
    private void convertHaspmapToObject(final Object obj,final HashMap<String, String> map)
        throws IntrospectionException, IllegalArgumentException, IllegalAccessException, InvocationTargetException
        {
        BeanInfo beanInfo=Introspector.getBeanInfo(obj.getClass());
        PropertyDescriptor propertys[]=beanInfo.getPropertyDescriptors();
        for(PropertyDescriptor p:propertys ){
            if(map.containsKey(p.getName())){
                //类型转换
                Class type=p.getPropertyType();
                ITypeConverter typeConver=ConverterFactory.getDefaultConverter();
                Object temp=typeConver.convertValue(map.get(p.getName()), type);

                p.getWriteMethod().invoke(obj, temp);
            }
        }
    }

    /**
     * Function:生成一个不存在的文件名，并返回一个包括路径和文件名的ImageVO对象。<br>
     * 			生成策略：根据时间得到随机字符串，再对此进行判断，如果存在重新再取<br>
     * @param path		文件存放的路径
     * @param suffix 文件的抬缀名
     * Create author:罗军林
     * Create on:2008-6-25
     * Edit author:
     * Edit on:
     * Why:
     * @throws IOException
     */
    private ImageVO generationNotExistFileName(final String path,final String suffix) throws IOException {
        File file=null;
        ImageVO imageVO=new ImageVO();
        imageVO.setPath(path);
        do{
            imageVO.setName(generationFileName()+suffix);
            file=new File(getUploadFilePath()+path+imageVO.getName());
        }while(file.exists());

        return imageVO;
    }

    /**
     * Function: 根据时间序列得到一个随机字符串
     *           示例：20080623123456789
     * @return
     * Create author:罗军林
     * Create on:2008-6-25
     * Edit author:
     * Edit on:
     * Why:
     */
    private String generationFileName() {
        Random rand=new Random(System.currentTimeMillis());
        SimpleDateFormat stringFormat=new SimpleDateFormat("yyyyMMdd");
        String date=stringFormat.format(new Date());
        int temp=rand.nextInt();
        while(temp<0){
            temp=rand.nextInt();
        }
        return date+temp;
    }

    /**
     * 功能描述:根据request，如果有input file域则上传，并返回上传之后的imageVO;其它表单域值将放入HashMap中<br>
     * @param request
     * @param fieldValue
     * @param imageList
     * @throws Throwable<br>
     * Create author:罗军林<br>
     * Create on:2008-7-21<br>
     * Edit author:<br>
     * Edit on:<br>
     * Why:<br>
     */
    private void upLoadFiles(final HttpServletRequest request,final HashMap<String, String> fieldValue,final List<ImageVO> imageList) throws Throwable{
        List fileItems = getFieldList(request);
        Iterator iter = fileItems.iterator();
        while (iter.hasNext()) {
            FileItem item = (FileItem)iter.next();
            if ( item.isFormField() ) {
                fieldValue.put(item.getFieldName(), item.getString(request.getCharacterEncoding()).trim());
            }else{
                checkFileType(item.getName());
                doUpLoadFile(imageList, item);
            }
        }
    }

    /**
     * 功能描述:检测文件类型是否在允许的范围之内<br>
     * @param name
     * @throws Exception
     * @throws Exception<br>
     * Create author:罗军林<br>
     * Create on:2008-7-22<br>
     * Edit author:<br>
     * Edit on:<br>
     * Why:<br>
     */
    private  void checkFileType(final String name) throws Exception{
        if(null == allowUploadFileTypes || allowUploadFileTypes.length()<=0 ) return;
        String[] fileTypes=allowUploadFileTypes.toLowerCase(Locale.ENGLISH).split(",");
        for(String typeItem : fileTypes){
            if(name.endsWith("."+typeItem)){
                return ;
            }
        }
        throw new Exception("不充许上传该文件");
    }

	/** 功能描述:路径解析。返回根据操作系统的不同所识别的路径<br>
	 * @param path<br>
	 * Create author:罗军林<br>
	 * Create on:2008-7-24<br>
	 * Edit author:<br>
	 * Edit on:<br>
	 * Why:<br>
	 */
	public static String parsePath(final String path) {
		String temp=null;
		if (File.separator.equals("\\")) {
			temp=path.replaceAll("/+", "\\\\");
		}
		if (File.separator.equals("/")) {
			temp=path.replaceAll("\\\\+", "/");
		}
		return temp;
	}

	/** 功能描述:根据path,name得到该文件对象<br>
	 * @param path 此路径不包括配置文件的前缀
	 * @param name
	 * @return<br>
	 * Create author:罗军林<br>
	 * Create on:2008-7-24<br>
	 * Edit author:<br>
	 * Edit on:<br>
	 * Why:<br>
	 */
	private static File getFileSystem(final String path, final String name) {
		StringBuffer pathFileName=new StringBuffer();
    	pathFileName.append(getUploadFilePath());
    	pathFileName.append(path);
    	pathFileName.append(name);

        File file=new File(parsePath(pathFileName.toString()));
		return file;
	}
    /****************************************/
    public long getSizeMax() {
        return sizeMax;
    }

    public void setSizeMax(long sizeMax) {
        this.sizeMax = sizeMax;
    }

    public int getSizeThreshold() {
        return sizeThreshold;
    }

    public void setSizeThreshold(int sizeThreshold) {
        this.sizeThreshold = sizeThreshold;
    }

    public String getUploadImagesPath() {
        return uploadImagesPath;
    }

    public void setUploadImagesPath(String uploadImagesPath) {
        this.uploadImagesPath = uploadImagesPath;
    }

    /**
     * @param allowUploadFileTypes the allowUploadFileTypes to set
     */
    public void setAllowUploadFileTypes(String allowUploadFileTypes) {
        this.allowUploadFileTypes = allowUploadFileTypes;
    }

    /**
     * @return the allowUploadFileTypes
     */
    public String getAllowUploadFileTypes() {
        return allowUploadFileTypes;
    }
}

/**
 * 功能描述：属性加载管理器，维护单一实例
 * @author 罗军林 创建日期：2008-7-22
 */
class PropertiesManage{
    private static Properties propert;

    private PropertiesManage(){}

    /** 功能描述:加载上传文件的配置文件,返回属性类<br>
     * @throws IOException<br>
     * Create author:罗军林<br>
     * Create on:2008-7-21<br>
     * Edit author:<br>
     * Edit on:<br>
     * Why:<br>
     */
    synchronized public static Properties getInstance(final String configPath){
    	InputStream in=null;
    	try{
            if(propert==null){
                propert=new Properties();
                ClassPathResource cpr = new ClassPathResource(configPath);
                in=cpr.getInputStream();
                propert.load(in);
            }
            return propert;
        }catch (IOException e) {
            e.printStackTrace();
            propert=null;
            return propert;
        } finally {
        	if (in!=null) {
				try {
					in.close();
					in=null;
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
        }
    }

}